import copy

import torch
import torch.nn as nn
import numpy
from ai.data_center import Data_Center
from database.dbop import Database
import database.sql as Sql
import src.global_config as gl
import json
from src.go import Go
import src.tool as tool

DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")


# 终于写到围棋ai的核心模块啦
class Honinbo:
    board = []
    color = None
    string = {}
    robX = None
    robY = None
    go = None
    data_center = None
    models = None
    feature = None
    policy = None
    value = None
    device = None
    ai_config = None
    model_path_config = None
    inplanes = None

    def __init__(self):

        self.go = Go()
        self.data_center = Data_Center()
        # 初始化围棋相关
        self.board = self.go.getEmptyBoard()
        self.string = self.go.getEmptyString()
        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        self.get_model()

    # 对接收到的信息做处理
    def parse_operator(self, data):
        # 收到的数据转json
        # print("honinbo收到的原生数据为", data)
        data_dict = json.loads(data)
        # 转换成引擎识别的数据
        # 引擎分析

        if data_dict['operator'] == 'run':
            self.parse_step_data(data_dict)
            # temp_string = data_dict['string']
            goban = data_dict['goban']
            legal, probas, winner,string,board = self.do_analyze(goban)
            return {
                "code": 0,
                "message": "success",
                "data": legal
            }
        # 保存棋谱
        elif data_dict['operator'] == 'saveGoban':
            goban = data_dict['goban']
            print("收到的棋谱为", goban)
            if len(goban) == 0:
                return {
                    "code": -1,
                    "message": "空棋谱不进行处理"
                }
            # 棋谱是单独的json 再解析一次
            goban = json.loads(goban)
            parsed = self.go.parse_goban_to_sgf(goban)['sgf']
            additional = self.go.parse_additional_sgf(goban)
            res = self.data_center.save_goban(parsed, additional)
            return res

    # 棋盘数据转张量
    def parse_board_to_tensor(self, option):

        if option == 'float':
            return torch.tensor(self.board, dtype=torch.float)
        else:
            return torch.tensor(self.board, dtype=torch.int)

    # 获取引擎需要的数据
    def parse_step_data(self, raw_data):
        self.board = raw_data['board']
        self.color = raw_data['color']

    def init_go_data(self):
        self.board = []
        self.color = None
        self.string = {}

    def set_models(self, dicts):
        self.models = dicts

    # 获取神经网络
    def get_model(self):
        print("DEVICE = ", DEVICE)
        self.ai_config = gl.get_value('ai')
        self.model_path_config = gl.get_value('model_path')
        self.inplanes = int(tool.get_config_by_key(self.ai_config, 'inplane'))
        feature_path = tool.get_config_by_key(self.model_path_config, 'feature')
        policy_path = tool.get_config_by_key(self.model_path_config, 'policy')
        value_path = tool.get_config_by_key(self.model_path_config, 'value')

        self.feature = torch.load(feature_path, map_location=DEVICE)
        self.policy = torch.load(policy_path, map_location=DEVICE)
        self.value = torch.load(value_path, map_location=DEVICE)

        print("获取神经网络权重成功")

    # 获取需要神经网络分析的数据结构
    def get_predict_data(self, board_list: list):
        self.go.init()
        board = self.board
        color = self.color
        state = []
        if color == 'black':
            color_num = 1
            my_color = 1
            oppo_color = -1
        else:
            color_num = 0
            my_color = 0
            oppo_color = 1

        # 这里取最近7个棋盘状态
        my_last, oppo_last = self.go.get_last_board(board_list, len(board_list) - 1, 1, 1)
        # 取了当前局面,那么预测就是当前局面的下一手棋
        # 将原生的棋盘数据 抽成 将来落子方的棋盘 对手方的棋盘，其他位置全0处理
        myself_board, oppo_board = self.go.get_myself_and_oppo_board(board, my_color, oppo_color)
        color_board = self.go.get_current_color_board(my_color)

        my = [myself_board]
        my.extend(my_last)
        oppo = [oppo_board]
        oppo.extend(oppo_last)
        my.extend(oppo)
        my.append(color_board)
        state.append(my)
        state = torch.tensor(state, dtype=torch.float, device=DEVICE)
        # 1 inplanes 19 19
        state = state.reshape(1, self.inplanes, 19, 19)
        return state

    # 分析
    def analyze(self, state) -> tuple:
        feature_maps = self.feature(state.clone().detach())
        winner = self.value(feature_maps)
        probas = self.policy(feature_maps)
        return probas, winner

    # 将引擎分析的内容转换为前端可读的数据
    @staticmethod
    def transfer_analyze_to_list(probas, winner):
        probas = probas.tolist()
        # 因为获取的是 [[]] 所以拿第一个
        probas = probas[0]
        winner = winner.tolist()
        result_list = []
        for i in range(len(probas)):
            x = i % 19
            y = int(i / 19)
            single_probas = probas[i]
            result_list.append({
                'x': x,
                'y': y,
                'probas': single_probas
            })
        # print("result_list ", result_list)

        # 根据probas从大到小排序
        result_list = sorted(result_list, reverse=True, key=lambda e: e.__getitem__('probas'))
        return result_list

    def do_analyze(self, goban: list) -> tuple:

        # 棋盘数据转张量，参数int或float 不传默认int
        self.board, self.string, self.robX, self.robY, board_list = self.get_string_and_board_from_front(
            goban=goban)
        state = self.get_predict_data(board_list)

        # 分析 然后获取合法的落子点
        probas, winner = self.analyze(state)
        result = self.transfer_analyze_to_list(probas, winner)
        legal = self.get_legal_moves(result)
        return legal, probas, winner, self.string, self.board

    # 获取合法的落子点
    def get_legal_moves(self, all_move: list) -> list:
        threshold = int(tool.get_config_by_key(self.ai_config, 'THRESHOLD'))

        res = []
        nums = 0
        illegals = []
        for i in range(len(all_move)):
            if nums >= threshold:
                break
            single = all_move[i]
            x = single['x']
            y = single['y']
            # 初始化
            self.go.board = copy.deepcopy(self.board)
            self.go.string = copy.deepcopy(self.string)
            self.go.robX = self.robX
            self.go.robY = self.robY
            success, board, string, temp_robX, temp_robY = self.go.go_logic(x, y, color=self.color)
            if success:
                res.append({
                    'x': x,
                    'y': y
                })
                nums = nums + 1
            else:
                illegals.append({
                    'x': x,
                    'y': y
                })
        print("本次获取的非法落子点 ->", illegals)
        print("非法落子点数量", len(illegals))
        return res

    # 根据前端发来的棋谱 获取棋盘和棋串
    def get_string_and_board_from_front(self, goban):
        self.go.init()
        board = None
        string = None
        robX = None
        robY = None
        board_list = []
        if len(goban) == 0:
            return self.go.getEmptyBoard(), self.go.getEmptyString(), None, None, board_list
        for i in range(len(goban)):
            step = goban[i]
            x = step['x']
            y = step['y']
            color = step['color']
            color_text = self.go.get_color_text_by_num(color)
            success, board, string, robX, robY = self.go.go_logic(x, y, color_text)
            board_list.append(board)
            if success is not True:
                print("step = {},x={},y={},color = {}".format(step, x, y, color))
                raise "组成围棋逻辑出现问题"
        return board, string, robX, robY, board_list
